/*
* Created on Jun 19, 2004
* 
* Copyright (C) 2003
*
* $Id: Channel.java,v 1.8 2005/12/04 20:56:26 cawe Exp $
*/
/*
Copyright (C) 1997-2001 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
using System;
using Defines = Suake2.UI.Defines;
using Globals = Suake2.UI.Globals;
using CL_ents = Suake2.UI.client.CL_ents;
using entity_state_t = Suake2.UI.game.entity_state_t;
using Com = Suake2.UI.qcommon.Com;
using Suake2.UI.sound;
using Lib = Suake2.UI.util.Lib;
using Math3D = Suake2.UI.util.Math3D;
//UPGRADE_TODO: The package 'java.nio' could not be found. If it was not included in the conversion, there may be compiler issues. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1262'"
using java.nio;
using AL10 = org.lwjgl.openal.AL10;
namespace Suake2.UI.sound.lwjgl
{
	
	/// <summary> Channel
	/// 
	/// </summary>
	/// <author>  dsanders/cwei
	/// </author>
	public class Channel
	{
		
		internal const int LISTENER = 0;
		internal const int FIXED = 1;
		internal const int DYNAMIC = 2;
		internal const int MAX_CHANNELS = 32;
		//UPGRADE_NOTE: Final was removed from the declaration of 'NULLVECTOR '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
		//UPGRADE_NOTE: The initialization of  'NULLVECTOR' was moved to static method 'jake2.sound.lwjgl.Channel'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		private static readonly FloatBuffer NULLVECTOR;
		
		private static Channel[] channels = new Channel[MAX_CHANNELS];
		//UPGRADE_NOTE: The initialization of  'sources' was moved to static method 'jake2.sound.lwjgl.Channel'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		private static IntBuffer sources;
		// a reference of L:WJGLSoundImpl.buffers 
		private static IntBuffer buffers;
		private static System.Collections.IDictionary looptable = System.Collections.Hashtable.Synchronized(new System.Collections.Hashtable(MAX_CHANNELS));
		
		private static bool isInitialized = false;
		private static int numChannels;
		
		// stream handling
		private static bool streamingEnabled = false;
		private static int streamQueue = 0;
		
		// sound attributes
		private int type;
		private int entnum;
		private int entchannel;
		private int bufferId;
		private int sourceId;
		private float volume;
		private float rolloff;
		private float[] origin = new float[]{0, 0, 0};
		
		// update flags
		private bool autosound;
		private bool active;
		private bool modified;
		private bool bufferChanged;
		private bool volumeChanged;
		
		private Channel(int sourceId)
		{
			this.sourceId = sourceId;
			clear();
			volumeChanged = false;
			volume = 1.0f;
		}
		
		private void  clear()
		{
			entnum = entchannel = bufferId = - 1;
			bufferChanged = false;
			rolloff = 0;
			autosound = false;
			active = false;
			modified = false;
		}
		
		//UPGRADE_NOTE: The initialization of  'tmp' was moved to static method 'jake2.sound.lwjgl.Channel'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		private static IntBuffer tmp;
		
		internal static int init(IntBuffer buffers)
		{
			Channel.buffers = buffers;
			// create channels
			int sourceId;
			for (int i = 0; i < MAX_CHANNELS; i++)
			{
				
				AL10.alGenSources(tmp);
				sourceId = tmp.get_Renamed(0);
				
				// can't generate more sources 
				if (sourceId <= 0)
					break;
				
				sources.put(i, sourceId);
				
				channels[i] = new Channel(sourceId);
				numChannels++;
				
				// set default values for AL sources
				AL10.alSourcef(sourceId, AL10.AL_GAIN, 1.0f);
				AL10.alSourcef(sourceId, AL10.AL_PITCH, 1.0f);
				AL10.alSourcei(sourceId, AL10.AL_SOURCE_ABSOLUTE, AL10.AL_TRUE);
				AL10.nalSourcefv(sourceId, AL10.AL_VELOCITY, NULLVECTOR, 0);
				AL10.alSourcei(sourceId, AL10.AL_LOOPING, AL10.AL_FALSE);
				AL10.alSourcef(sourceId, AL10.AL_REFERENCE_DISTANCE, 200.0f);
				AL10.alSourcef(sourceId, AL10.AL_MIN_GAIN, 0.0005f);
				AL10.alSourcef(sourceId, AL10.AL_MAX_GAIN, 1.0f);
			}
			isInitialized = true;
			return numChannels;
		}
		
		internal static void  reset()
		{
			for (int i = 0; i < numChannels; i++)
			{
				AL10.alSourceStop(sources.get_Renamed(i));
				AL10.alSourcei(sources.get_Renamed(i), AL10.AL_BUFFER, 0);
				channels[i].clear();
			}
		}
		
		internal static void  shutdown()
		{
			AL10.alDeleteSources(sources);
			numChannels = 0;
			isInitialized = false;
		}
		
		internal static void  enableStreaming()
		{
			if (streamingEnabled)
				return ;
			
			// use the last source
			numChannels--;
			streamingEnabled = true;
			streamQueue = 0;
			
			int source = channels[numChannels].sourceId;
			AL10.alSourcei(source, AL10.AL_SOURCE_RELATIVE, AL10.AL_TRUE);
			AL10.alSourcef(source, AL10.AL_GAIN, 1.0f);
			channels[numChannels].volumeChanged = true;
			
			Com.DPrintf("streaming enabled\n");
		}
		
		internal static void  disableStreaming()
		{
			if (!streamingEnabled)
				return ;
			unqueueStreams();
			int source = channels[numChannels].sourceId;
			AL10.alSourcei(source, AL10.AL_SOURCE_ABSOLUTE, AL10.AL_TRUE);
			
			// free the last source
			numChannels++;
			streamingEnabled = false;
			Com.DPrintf("streaming disabled\n");
		}
		
		internal static void  unqueueStreams()
		{
			if (!streamingEnabled)
				return ;
			int source = channels[numChannels].sourceId;
			
			// stop streaming
			AL10.alSourceStop(source);
			int count = AL10.alGetSourcei(source, AL10.AL_BUFFERS_QUEUED);
			Com.DPrintf("unqueue " + count + " buffers\n");
			while (count-- > 0)
			{
				AL10.alSourceUnqueueBuffers(source, tmp);
			}
			streamQueue = 0;
		}
		
		internal static void  updateStream(ByteBuffer samples, int count, int format, int rate)
		{
			enableStreaming();
			int source = channels[numChannels].sourceId;
			int processed = AL10.alGetSourcei(source, AL10.AL_BUFFERS_PROCESSED);
			
			bool playing = (AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING);
			bool interupted = !playing && streamQueue > 2;
			
			IntBuffer buffer = tmp;
			if (interupted)
			{
				unqueueStreams();
				buffer.put(0, buffers.get_Renamed(jake2.sound.Sound_Fields.MAX_SFX + streamQueue++));
				Com.DPrintf("queue " + (streamQueue - 1) + '\n');
			}
			else if (processed < 2)
			{
				// check queue overrun
				if (streamQueue >= jake2.sound.Sound_Fields.STREAM_QUEUE)
					return ;
				buffer.put(0, buffers.get_Renamed(jake2.sound.Sound_Fields.MAX_SFX + streamQueue++));
				Com.DPrintf("queue " + (streamQueue - 1) + '\n');
			}
			else
			{
				// reuse the buffer
				AL10.alSourceUnqueueBuffers(source, buffer);
			}
			
			samples.position(0);
			samples.limit(count);
			AL10.alBufferData(buffer.get_Renamed(0), format, samples, rate);
			AL10.alSourceQueueBuffers(source, buffer);
			
			if (streamQueue > 1 && !playing)
			{
				Com.DPrintf("start sound\n");
				AL10.alSourcePlay(source);
			}
		}
		
		internal static void  addPlaySounds()
		{
			while (Channel.assign(PlaySound.nextPlayableSound()))
				;
		}
		
		private static bool assign(PlaySound ps)
		{
			if (ps == null)
				return false;
			Channel ch = null;
			int i;
			for (i = 0; i < numChannels; i++)
			{
				ch = channels[i];
				
				if (ps.entchannel != 0 && ch.entnum == ps.entnum && ch.entchannel == ps.entchannel)
				{
					// always override sound from same entity
					if (ch.bufferId != ps.bufferId)
					{
						AL10.alSourceStop(ch.sourceId);
					}
					break;
				}
				
				// don't let monster sounds override player sounds
				if ((ch.entnum == Globals.cl.playernum + 1) && (ps.entnum != Globals.cl.playernum + 1) && ch.bufferId != - 1)
					continue;
				
				// looking for a free AL source
				if (!ch.active)
				{
					break;
				}
			}
			
			if (i == numChannels)
				return false;
			
			ch.type = ps.type;
			if (ps.type == Channel.FIXED)
				Math3D.VectorCopy(ps.origin, ch.origin);
			ch.entnum = ps.entnum;
			ch.entchannel = ps.entchannel;
			ch.bufferChanged = (ch.bufferId != ps.bufferId);
			ch.bufferId = ps.bufferId;
			ch.rolloff = ps.attenuation * 2;
			ch.volumeChanged = (ch.volume != ps.volume);
			ch.volume = ps.volume;
			ch.active = true;
			ch.modified = true;
			return true;
		}
		
		private static Channel pickForLoop(int bufferId, float attenuation)
		{
			Channel ch;
			for (int i = 0; i < numChannels; i++)
			{
				ch = channels[i];
				// looking for a free AL source
				if (!ch.active)
				{
					ch.entnum = 0;
					ch.entchannel = 0;
					ch.bufferChanged = (ch.bufferId != bufferId);
					ch.bufferId = bufferId;
					ch.volumeChanged = (ch.volume != 1.0f);
					ch.volume = 1.0f;
					ch.rolloff = attenuation * 2;
					ch.active = true;
					ch.modified = true;
					return ch;
				}
			}
			return null;
		}
		
		//UPGRADE_NOTE: The initialization of  'sourceOriginBuffer' was moved to static method 'jake2.sound.lwjgl.Channel'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1005'"
		private static FloatBuffer sourceOriginBuffer;
		
		//stack variable
		private static float[] entityOrigin = new float[]{0, 0, 0};
		
		internal static void  playAllSounds(FloatBuffer listenerOrigin)
		{
			FloatBuffer sourceOrigin = sourceOriginBuffer;
			Channel ch;
			int sourceId;
			int state;
			
			for (int i = 0; i < numChannels; i++)
			{
				ch = channels[i];
				if (ch.active)
				{
					sourceId = ch.sourceId;
					switch (ch.type)
					{
						
						case Channel.LISTENER: 
							sourceOrigin.put(0, listenerOrigin.get_Renamed(0));
							sourceOrigin.put(1, listenerOrigin.get_Renamed(1));
							sourceOrigin.put(2, listenerOrigin.get_Renamed(2));
							break;
						
						case Channel.DYNAMIC: 
							CL_ents.GetEntitySoundOrigin(ch.entnum, entityOrigin);
							convertVector(entityOrigin, sourceOrigin);
							break;
						
						case Channel.FIXED: 
							convertVector(ch.origin, sourceOrigin);
							break;
						}
					
					if (ch.modified)
					{
						if (ch.bufferChanged)
						{
							AL10.alSourcei(sourceId, AL10.AL_BUFFER, ch.bufferId);
						}
						if (ch.volumeChanged)
						{
							AL10.alSourcef(sourceId, AL10.AL_GAIN, ch.volume);
						}
						AL10.alSourcef(sourceId, AL10.AL_ROLLOFF_FACTOR, ch.rolloff);
						AL10.nalSourcefv(sourceId, AL10.AL_POSITION, sourceOrigin, 0);
						AL10.alSourcePlay(sourceId);
						ch.modified = false;
					}
					else
					{
						state = AL10.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE);
						if (state == AL10.AL_PLAYING)
						{
							AL10.nalSourcefv(sourceId, AL10.AL_POSITION, sourceOrigin, 0);
						}
						else
						{
							ch.clear();
						}
					}
					ch.autosound = false;
				}
			}
		}
		
		/*
		* 	adddLoopSounds
		* 	Entities with a ->sound field will generated looped sounds
		* 	that are automatically started, stopped, and merged together
		* 	as the entities are sent to the client
		*/
		internal static void  addLoopSounds()
		{
			
			if ((Globals.cl_paused.value_Renamed != 0.0f) || (Globals.cls.state != Globals.ca_active) || !Globals.cl.sound_prepped)
			{
				removeUnusedLoopSounds();
				return ;
			}
			
			Channel ch;
			sfx_t sfx;
			sfxcache_t sc;
			int num;
			entity_state_t ent;
			System.Object key;
			int sound = 0;
			
			for (int i = 0; i < Globals.cl.frame.num_entities; i++)
			{
				num = (Globals.cl.frame.parse_entities + i) & (Defines.MAX_PARSE_ENTITIES - 1);
				ent = Globals.cl_parse_entities[num];
				sound = ent.sound;
				
				if (sound == 0)
					continue;
				
				key = (System.Int32) ent.number;
				ch = (Channel) looptable[key];
				
				if (ch != null)
				{
					// keep on looping
					ch.autosound = true;
					Math3D.VectorCopy(ent.origin, ch.origin);
					continue;
				}
				
				sfx = Globals.cl.sound_precache[sound];
				if (sfx == null)
					continue; // bad sound effect
				
				sc = sfx.cache;
				if (sc == null)
					continue;
				
				// allocate a channel
				ch = Channel.pickForLoop(buffers.get_Renamed(sfx.bufferId), 6);
				if (ch == null)
					break;
				
				ch.type = FIXED;
				Math3D.VectorCopy(ent.origin, ch.origin);
				ch.autosound = true;
				
				looptable[key] = ch;
				AL10.alSourcei(ch.sourceId, AL10.AL_LOOPING, AL10.AL_TRUE);
			}
			
			removeUnusedLoopSounds();
		}
		
		private static void  removeUnusedLoopSounds()
		{
			Channel ch;
			// stop unused loopsounds
			//UPGRADE_TODO: Method 'java.util.Iterator.hasNext' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratorhasNext'"
			for (System.Collections.IEnumerator iter = looptable.Values.GetEnumerator(); iter.MoveNext(); )
			{
				//UPGRADE_TODO: Method 'java.util.Iterator.next' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javautilIteratornext'"
				ch = (Channel) iter.Current;
				if (!ch.autosound)
				{
					AL10.alSourceStop(ch.sourceId);
					AL10.alSourcei(ch.sourceId, AL10.AL_LOOPING, AL10.AL_FALSE);
					//UPGRADE_ISSUE: Method 'java.util.Iterator.remove' was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1000_javautilIteratorremove'"
					iter.remove();
					ch.clear();
				}
			}
		}
		
		internal static void  convertVector(float[] from, FloatBuffer to)
		{
			to.put(0, from[0]);
			to.put(1, from[2]);
			to.put(2, - from[1]);
		}
		
		internal static void  convertOrientation(float[] forward, float[] up, FloatBuffer orientation)
		{
			orientation.put(0, forward[0]);
			orientation.put(1, forward[2]);
			orientation.put(2, - forward[1]);
			orientation.put(3, up[0]);
			orientation.put(4, up[2]);
			orientation.put(5, - up[1]);
		}
		static Channel()
		{
			NULLVECTOR = Lib.newFloatBuffer(3);
			sources = Lib.newIntBuffer(MAX_CHANNELS);
			tmp = Lib.newIntBuffer(1);
			sourceOriginBuffer = Lib.newFloatBuffer(3);
		}
	}
}